#include <stdio.h>
#include <stdlib.h>
#include "praid.h"
#include "Utiles.h"

#define BACKLOG 10

int main(int argc, char *argv[]) {
	int sockEscucha, res;
	char msgAux[256]; //Para Guardar en el Log
	struct sockaddr_in serv_addr, remoteDirCliente;
	int bytesleidos; // cantidad de caracteres leidos o escritos
	int yes=1;
	char auxLog[100], idPPD[5], *id;
	socklen_t sin_size=sizeof(struct sockaddr_in); /* GUARDO EL TAMAÑO DE LA ESTRUCTURA SOCKADDR_IN*/

	//ST_SECTOR			*unSector; //Para manejar los envios de Respuestas
	//ST_RESPUESTA		rtaSector;


	//Listas
	lPPd *ListaPFSAux;
	ListaPFSAux = ptrPPD; //Recorrera la lista de PPD conectados para escribirlos cuando PFS pide escritura

	system("clear");

	cargarConfiguracion(&stPRAID);

	iniciarLog( "praidLog.txt" );

	//CONSOLA
	if (stPRAID.iFlag == 1)
	{
		printf("=====================================================================\n");
		printf("\t\t===EL PROCESO RAID ENTRA EN FUNCIONAMIENTO===\n");
		printf("=====================================================================\n");
		log("praidLog.txt","PRAID","INFO","===EL PROCESO RAID ENTRA EN FUNCIONAMIENTO===\n");
	}

	printf("=====================================================================\n");
	printf("\t\t Proceso RAID (PRAID)\n IP: %s\n PUERTO: %i\n FLAG: %i\n SINCRO: %i\n",stPRAID.szIp, stPRAID.iPuerto, stPRAID.iFlag, stPRAID.iSinc);
	printf("=====================================================================\n");

	/* CONEXION Planificador De Disco */
	/*----------------------------------------------------------------------------------------------------------------------------------------*/

	if ((sockEscucha = socket(AF_INET, SOCK_STREAM, 0)) == -1)
	{
		log( "praidLog.txt", "PRAID", "ERROR", "Error creando socket de escucha");
		exit(EXIT_FAILURE);
	}

	/*  ARREGLO  "address already in use" */
	if (setsockopt(sockEscucha, SOL_SOCKET, SO_REUSEADDR, &yes,sizeof(int)) == -1)
	{
		log( "praidLog.txt", "PRAID", "ERROR", "Error en setsockopt");
		exit(EXIT_FAILURE);
	}

	/* LLENO LA ESTRUCTURA LOCAL*/
	//serv_addr.sin_addr.s_addr = INADDR_ANY; //inet_addr(stPRAID.ipLocal) Usar IP desde archConf
	serv_addr.sin_addr.s_addr = inet_addr(stPRAID.szIp);
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_port = htons(stPRAID.iPuerto);
	memset(&(serv_addr.sin_zero), '\0', 8);

	/*  BINDEO EL SOCKET*/
	if (bind(sockEscucha, (struct sockaddr *)&serv_addr, sizeof(struct sockaddr)) == -1)
	{
		log( "praidLog.txt", "PRAID", "ERROR", "Error en el BIND de socket");
		exit(EXIT_FAILURE);
	}

	/* PONGO EL SOCKET EN ESCUCHA*/
	if (listen(sockEscucha, BACKLOG) == -1)
	{
		log( "praidLog.txt", "PRAID", "ERROR", "Error en listen socket");
		exit(EXIT_FAILURE);
	}

	/* TRABAJO CON LA BOLSA DE FD.*/
	/*----------------------------------------*/

	FD_ZERO(&readfds); 					/*LIMPIO*/
	FD_ZERO(&master);  					/*LIMPIO*/
	FD_SET(sockEscucha,&master); 		/*AGREGO SOCKET ESCUCHA A LA BOLSA*/
	fdmax = sockEscucha;

	while(1){

		TimeoutSelect.tv_sec = 1;
		TimeoutSelect.tv_usec = 0;

		/* GUARDO LA BOLSA MAESTRA*/
		readfds = master;
		memset( &remoteDirCliente, '\0', sin_size ); /*Inicializo la estructura*/

		/* BUSCO LOS FD EN LOS CUALES PASO ALGO*/
		if (select(fdmax+1, &readfds, NULL, NULL, NULL)== -1)
		{
			log( "praidLog.txt", "PRAID", "ERROR", "Error en Select");
			perror("Error en SELECT");
			exit(1);
		}
		else{
			/*  ME FIJO EN TODOS LOS FD SI PASO ALGO*/
			for(i = 0; i <= fdmax; i++)
			{

				/*  VEO SI TENGO DATOS*/
				if (FD_ISSET(i, &readfds))
				{
					/* ME FIJO SI ES UNA CONEXION NUEVA*/
					if (i == sockEscucha)
					{
						/*  ACEPTO LA CONEXION*/
						if ((newSock = accept(sockEscucha, (struct sockaddr*)&remoteDirCliente,&sin_size)) == -1)
						{
							log( "praidLog.txt", "PRAID", "ERROR", "Error en Accept");
						}
						else
						{
							int flag = 1;
							int result = setsockopt(newSock,         /* socket affected */
							                        IPPROTO_TCP,     /* set option at TCP level */
							                        TCP_NODELAY,     /* name of option */
							                        (char *) &flag,  /* the cast is historical cruft */
							                        sizeof(int));    /* length of option value */

							/*Controlo recepcion de Handshake por ser nueva conexion*/
							if(recv(newSock,(void*) &stCabecera, sizeof(ST_NIPC),MSG_WAITALL) <= 0)
							{
								close (newSock);
								FD_CLR(newSock, &master); // eliminar del conjunto maestro
								log("praidLog.txt","PRAID","ERROR","Error al recibir Mensaje de nueva conexion");
								continue;
							}
							//printf("Recibi algo\n");
							/*Controlo que lo que recibo sea un Handshake*/
							if(stCabecera.ucType == HANDSHAKE)
							{
								/*Puede ser un PFS*/
								if(stCabecera.lPayload == 0)
								{
									/*Controlo que existan al menos 2 PPD conectados al RAID para aceptar el PFS*/
									if(cantNodoListaPPD() < 1)
									{
										// /*Caso negativo, se rechaza la conexion con PFS*/
										stCabecera.ucType = FAIL;
										strcpy(auxLog,"Se RECHAZO conexion PFS");
									}
									else
									{
										/*Caso positivo, se acepta la conexion con PFS*/
										stCabecera.ucType = PRAID;
										strcpy(auxLog,"Se ACEPTO conexion PFS");
										FD_SET(newSock ,&master);
										if(newSock > fdmax)
										{
											fdmax = newSock;
										}
										sprintf(msgAux,"Nuevo PFS Conectado desde %s Por el socket %d",inet_ntoa(remoteDirCliente.sin_addr),newSock);
										printf("Nuevo PFS Conectado desde %s Por el socket %d\n ",inet_ntoa(remoteDirCliente.sin_addr),newSock );

									}
									/*Envio el send con el resultado de HANDSHAKE*/
									if( send( newSock, &stCabecera, sizeof(ST_NIPC), MSG_NOSIGNAL ) < 0 )
									{
										close( newSock );
										FD_CLR(newSock, &master); // eliminar del conjunto maestro
										log( "praidLog.txt", "PPD", "ERROR","Error en el SEND del HANDSHAKE");
										continue;
									}
									else
										log( "praidLog.txt", "PRAID", "INFO", auxLog);
								}
								/*Es PPD porque el payload lenght es distinto de cero*/
								else
								{
								
									strncpy(idPPD, stCabecera.buffer, 4);
									if(existePPD(idPPD) == 0){
								
										stCabecera.ucType = PRAID;

										if( send( newSock, &stCabecera, sizeof(ST_NIPC), MSG_NOSIGNAL) < 0 )
										{
											close( newSock );
											FD_CLR(newSock, &master); // eliminar del conjunto maestro
											log( "praidLog.txt", "PPD", "ERROR","Error en el SEND del HANDSHAKE");
											continue;
										}
										else
										{
											strcpy(auxLog,"Se ACEPTO conexion PPD");
											log( "praidLog.txt", "PRAID", "INFO", auxLog);
											FD_SET(newSock,&master);
											sprintf(msgAux,"Nuevo PPD Conectado desde %s Por el socket %d",inet_ntoa(remoteDirCliente.sin_addr),newSock);
											printf("%s\n",msgAux);
											if(newSock > fdmax)
											{
												fdmax = newSock;
											}
											

										}

									}
									else{
									
										stCabecera.ucType = FAIL;
										if( send( newSock, &stCabecera, sizeof(ST_NIPC), MSG_NOSIGNAL) < 0 )
										{
											close( newSock );
											FD_CLR(newSock, &master); // eliminar del conjunto maestro
											log( "praidLog.txt", "PPD", "ERROR","Error en el SEND del HANDSHAKE");
											continue;
										}
										log( "praidLog.txt", "PPD", "ERROR","IdPPD iguales Conection refused");
										close( newSock );
										FD_CLR(newSock, &master); // eliminar del conjunto maestro
									}

									//Inserto en la lista el discoID, socket, cantidad de Sectores.
									//Guardo el Payload que contendra el Identificador del disco que se conecta, en una lista
									res = InsertaListaPPD(newSock,stCabecera.buffer);

									if(res == -1)
										log( "praidLog.txt", "PPD", "ERROR", "Error al insertar el PPD");
									else{
										if(res == 1)
											log( "praidLog.txt", "PPD", "ERROR", "El PPD se inserto");
									}

									//Crear Thread por cada Disco
									if(stPRAID.iSinc == 1){
										printf("Sincro activada \n");
										if (sincronizarPPD() == -1){
										//Se controla la salida del HILO
											if (sincroError == -1){
												if(cantNodoListaPPD() == 0){
												 //No Encontro disco ya sincronizado
												 printf("Finalizo sincronizacion por error. No hay discos, se finaliza el PRAID");
												 return 0; //Se tiene que cerrar?
												}
											}
										}
									}
								}
							}

						}

					}
					else //conexiones existentes
					{
						if(i == buscarConexPPD(i)){

							bytesleidos = recv(i, (void *) &stCabecera, sizeof(ST_NIPC), MSG_WAITALL);
							
							if (bytesleidos <= 0){
								//conexión cerrada por el cliente
								if (bytesleidos == 0)
								{
									// conexión cerrada
									printf("selectserver: socket %d cerro la conexion\n", i);
								}
								else
								{
									log( "praidLog.txt", "PRAID", "ERROR", "Error en receive");
								}
								close(i); // bye!
								FD_CLR(i, &master); // eliminar del conjunto maestro
								eliminarPPD(i);
								replanificarPedidos(i);
								if(cantNodoListaPPD(i) == 0){
									printf("Se cierra todo porque no hay mas sincronizados\n");
									return -1;
								}
							}
							else{
								if(atenderMensajePPD(i,stCabecera) == -1){
									return 0;
								}
							}
						}
						else{
							bytesleidos = recv(i, (void *)&stCabecera, sizeof(ST_NIPC), MSG_WAITALL);
							if (bytesleidos <= 0){
								//conexión cerrada por el cliente
								if (bytesleidos == 0)
								{
									// conexión cerrada
									printf("selectserver: socket %d cerro la conexion\n", i);
								}
								else
								{
									log( "praidLog.txt", "PRAID", "ERROR", "Error en receive");
									perror("error de RECV");
								}
								close(i); // bye!
								FD_CLR(i, &master); // eliminar del conjunto maestro
							}
							else{

								atenderMensajePFS(i, stCabecera);

							}//fin else

						}

					}//fin de else conexiones existentes
				}//fin if isset
			}// fin de for

			 if (cantNodoListaPFS() > 0){ //tengo pedidos que atender
				  atenderPedPfs();
			 }
		}//fin de else select
	} //fin while(1)
	printf("Fin del process\n\n");
	return 0;
} // Fin del Main

